import 'package:flutter/material.dart';
import 'package:flutter_sensor_backend/assets/color.dart';

import 'package:sensors_plus/sensors_plus.dart';
import 'dart:math';

/// DeviceRotationDisplayStand
class DeviceRotationDisplayStand extends StatefulWidget {
  @override
  DeviceRotationDisplayStandState createState() =>
      DeviceRotationDisplayStandState();
}

/// DeviceRotationDisplayStandState
class DeviceRotationDisplayStandState
    extends State<DeviceRotationDisplayStand> {
  static double canvas_width = 260;
  static double canvas_height = 200;
  bool disposeFlag = false;

  @override
  void initState() {
    gyroscopeEvents.listen((event) {
      DeviceRotationHost.rotateWithAcceleration(
          event.x, event.y, event.z, 0.01);
      if (!disposeFlag) setState(() {});
    });

    super.initState();
  }

  @override
  void dispose() {
    disposeFlag = true;
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    canvas_width = size.width;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.center,
      children: [
        Container(
          child: GestureDetector(
            child: CustomPaint(
              isComplex: true,
              size: Size(canvas_width - 60, canvas_height),
              willChange: true,
              painter: Painter(),
            ),
            onTap: () => DeviceRotationHost.restore(),
          ),
        )
      ],
    );
  }
}

class Painter extends CustomPainter {
  var object_width = 100, object_height = 177;

  var Camera = Point(0, 0, 800);

  double abs(double num) => num >= 0 ? num : -num;

  //  yaw - pitch - roll
  Doubles3D GetRotationAngles() => DeviceRotationHost.getRotationAngles();

  Point? GetLeftBarPoint() => GetCrossPoint(
      Rotate(
          Point(-object_width / 2 + object_width / 4,
              -object_height / 2 + object_height / 24, 0),
          GetRotationAngles()),
      Camera,
      null,
      null);
  Point? GetRightBarPoint() => GetCrossPoint(
      Rotate(
          Point(object_width / 2 - object_width / 4,
              -object_height / 2 + object_height / 24, 0),
          GetRotationAngles()),
      Camera,
      null,
      null);

  Point? GetLeftTopPoint() => GetCrossPoint(
      Rotate(
          Point(-object_width / 2, object_height / 2, 0), GetRotationAngles()),
      Camera,
      null,
      null);
  Point? GetRightTopPoint() => GetCrossPoint(
      Rotate(
          Point(object_width / 2, object_height / 2, 0), GetRotationAngles()),
      Camera,
      null,
      null);
  Point? GetRightBottomPoint() => GetCrossPoint(
      Rotate(
          Point(object_width / 2, -object_height / 2, 0), GetRotationAngles()),
      Camera,
      null,
      null);
  Point? GetLeftBottomPoint() => GetCrossPoint(
      Rotate(
          Point(-object_width / 2, -object_height / 2, 0), GetRotationAngles()),
      Camera,
      null,
      null);

  Offset ToOffset(Point p) => Offset(p.x, p.y);
  Offset ToCenter(Offset p, double width, double height) => Offset(
      p.dx > 0 ? width / 2 + p.dx : width / 2 - abs(p.dx),
      p.dy > 0 ? height / 2 - p.dy : height / 2 + abs(p.dy));
  Offset ToScreen(Point p, Size size) =>
      ToCenter(ToOffset(p), size.width, size.height);

  @override
  void paint(Canvas canvas, Size size) {
    var paint = Paint()
      ..color = os_color
      ..style = PaintingStyle.fill
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 5.0;

    var a = GetLeftTopPoint();
    var b = GetRightTopPoint();
    var c = GetRightBottomPoint();
    var d = GetLeftBottomPoint();
    var e = GetLeftBarPoint();
    var f = GetRightBarPoint();
    if (a != null && b != null && c != null && d != null) {
      Path path = Path()..moveTo(ToScreen(a, size).dx, ToScreen(a, size).dy);
      path.lineTo(ToScreen(b, size).dx, ToScreen(b, size).dy);
      path.lineTo(ToScreen(c, size).dx, ToScreen(c, size).dy);
      path.lineTo(ToScreen(d, size).dx, ToScreen(d, size).dy);
      canvas.drawPath(path, paint);
    }
    if (e != null && f != null) {
      paint
        ..color = os_white
        ..strokeWidth = 3;
      canvas.drawLine(ToScreen(e, size), ToScreen(f, size), paint);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

class DeviceRotationHost {
  static double yaw = 0, pitch = 0, roll = 0;

  static double perRadToDegrees = 57.29577951308232;

  ///  显式调用此方法来计算加速度之后的旋转角
  ///  x, y, z 单位: rad/s, time 单位: s
  ///  yaw -> z
  ///  pitch -> y
  ///  roll -> x
  static void rotateWithAcceleration(
      double x, double y, double z, double time) {
    yaw += z * time * perRadToDegrees;
    pitch += x * time * perRadToDegrees;
    roll += y * time * perRadToDegrees;

    // print('yaw: $yaw, pitch: $pitch, roll: $roll');
  }

  static void restore() {
    yaw = 0;
    pitch = 0;
    roll = 0;
  }

  static Doubles3D getRotationAngles() => Doubles3D(yaw, pitch, roll);
}
// ignore_for_file: public_member_api_docs, non_constant_identifier_names

const double pi = 3.1415926535897938324626433832795028841971;

class Doubles3D {
  var x = 0.0, y = 0.0, z = 0.0;
  Doubles3D(this.x, this.y, this.z);
}

class Point extends Doubles3D {
  Point(super.x, super.y, super.z);
}

class Vector3D extends Doubles3D {
  Vector3D(super.x, super.y, super.z);
}

Point Rotate(Point p, Doubles3D angles) =>
    AllRotate(p, angles.x, angles.y, angles.z);

Point AllRotate(Point p, double yaw, double pitch, double roll) =>
    X_Rotate(Y_Rotate(Z_Rotate(p, yaw), pitch), roll);

Point X_Rotate(Point p, double alpha) {
  alpha *= pi / 180;
  return Point(cos(alpha) * p.x + sin(alpha) * p.z, p.y,
      -sin(alpha) * p.x + cos(alpha) * p.z);
}

Point Y_Rotate(Point p, double beta) {
  beta *= pi / 180;
  return Point(p.x, cos(beta) * p.y - sin(beta) * p.z,
      sin(beta) * p.y + cos(beta) * p.z);
}

Point Z_Rotate(Point p, double gamma) {
  gamma *= pi / 180;
  return Point(cos(gamma) * p.x - sin(gamma) * p.y,
      sin(gamma) * p.x + cos(gamma) * p.y, p.z);
}

Vector3D GetDirection(Point p1, Point p2) =>
    Vector3D(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z);

Point? GetCrossPoint(Point p, Point camera, Point? n, Vector3D? plane) {
  var VL = GetDirection(p, camera);
  var VP = plane ?? Vector3D(0, 0, 1);

  var vp1 = VP.x, vp2 = VP.y, vp3 = VP.z;
  var v1 = VL.x, v2 = VL.y, v3 = VL.z;

  var t_base = vp1 * v1 + vp2 * v2 + vp3 * v3;
  if (t_base - 0 <= 0.00001) {
    return null;
  } else {
    n ??= Point(0, 0, 0);

    var n1 = n.x, n2 = n.y, n3 = n.z;
    var p1 = p.x, p2 = p.y, p3 = p.z;

    var t_devide = (n1 - p1) * vp1 + (n2 - p2) * vp2 + (n3 - p3) * vp3;
    var t = t_devide / t_base;

    var x = p1 + v1 * t;
    var y = p2 + v2 * t;
    var z = p3 + v3 * t;

    return Point(x, y, z);
  }
}
